<template>
  <div class="pdf-viewer">
    <div
      v-for="(page, pIdx) in layout.pdf_info"
      :key="pIdx"
      class="page"
      :style="pageStyle(page)">

      <!-- 1. 文字块（含行内公式） -->
      <div
        v-for="(blk, bIdx) in blocksOf(page, ['text', 'title'])"
        :key="`t-${pIdx}-${bIdx}`"
        class="text-block"
        :style="blockStyle(blk)">
        <div :style="textStyle(blk)" class="inline-wrapper">
          <component
            :is="'span'"
            v-for="(node, nIdx) in renderBlock(blk)"
            :key="nIdx"
            v-bind="node.attrs"
            v-html="node.html" />
        </div>
      </div>

      <!-- 2. 行间公式（优先图片，无图则 KaTeX） -->
      <div
        v-for="(blk, bIdx) in blocksOf(page, ['interline_equation'])"
        :key="`eq-${pIdx}-${bIdx}`"
        class="scale-wrapper"
        :style="blockStyle(blk)">
        <div
          class="scale-inner"
          v-scale-katex="{
            content: blk.lines[0].spans[0].content,
            imagePath: blk.lines[0].spans[0].image_path
          }" />
      </div>

      <!-- 3. 图片 -->
      <div
        v-for="(blk, bIdx) in blocksOf(page, ['image'])"
        :key="`img-${pIdx}-${bIdx}`"
        class="scale-wrapper"
        :style="blockStyle(blk)">
        <img
          class="scale-inner"
          :src="blk.blocks[0].lines[0].spans[0].image_path"
          alt="figure"
          v-scale-img />
      </div>

      <!-- 4. 图片 caption -->
      <div
        v-for="(blk, bIdx) in blocksOf(page, ['image_caption'])"
        :key="`icap-${pIdx}-${bIdx}`"
        class="text-block"
        :style="blockStyle(blk)">
        <div :style="textStyle(blk)" class="inline-wrapper">
          <component
            :is="'span'"
            v-for="(node, nIdx) in renderBlock(blk)"
            :key="nIdx"
            v-bind="node.attrs"
            v-html="node.html" />
        </div>
      </div>

      <!-- 5. 表格 -->
      <div
        v-for="(blk, bIdx) in blocksOf(page, ['table'])"
        :key="`tb-${pIdx}-${bIdx}`"
        class="scale-wrapper"
        :style="blockStyle(blk)">
        <div
          class="scale-inner"
          v-scale-table="blk.blocks[0].lines[0].spans[0].html" />
      </div>

      <!-- 6. 表格 caption -->
      <div
        v-for="(blk, bIdx) in blocksOf(page, ['table_caption'])"
        :key="`tcap-${pIdx}-${bIdx}`"
        class="text-block"
        :style="blockStyle(blk)">
        <div :style="textStyle(blk)" class="inline-wrapper">
          <component
            :is="'span'"
            v-for="(node, nIdx) in renderBlock(blk)"
            :key="nIdx"
            v-bind="node.attrs"
            v-html="node.html" />
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import katex from 'katex'
import 'katex/dist/katex.min.css'

export default {
  name: 'PdfRenderer',
  props: {
    layout: {
      type: Object,
      default () {
        return {}
      }
    }
  },
  data () {
    return {
      fontMap: new Map(),
      minFS: 8,
      maxFS: 16,
      pageWidth: 1000,
      pageScale: 1
    }
  },
  mounted () {
    this.$nextTick(() => {
      this.calcAllFontSizes()
    })
  },
  methods: {
    // 递归收集指定类型的所有块（包括嵌套在 image/table 里的 caption）
    collectBlocks (paraBlocks, types, out = []) {
      paraBlocks.forEach(b => {
        if (types.includes(b.type)) out.push(b)
        if (b.blocks && Array.isArray(b.blocks)) {
          this.collectBlocks(b.blocks, types, out)
        }
      })
      return out
    },

    // 对外暴露的 blocksOf 接口
    blocksOf (page, types) {
      return this.collectBlocks(page.para_blocks, types)
    },

    pageStyle (page) {
      const [w, h] = page.page_size
      this.pageScale = this.pageWidth / w
      return {
        width: `${w * this.pageScale}px`,
        height: `${h * this.pageScale}px`,
        position: 'relative',
        margin: '0 auto 20px',
        background: '#dfdfdf'
      }
    },

    blockStyle (blk) {
      const [x, y, x2, y2] = blk.bbox
      return {
        position: 'absolute',
        left: `${x * this.pageScale}px`,
        top: `${y * this.pageScale}px`,
        width: `${(x2 - x + 5) * this.pageScale}px`,
        height: `${(y2 - y) * this.pageScale}px`
      }
    },

    textStyle (blk) {
      const fs = this.fontMap.get(blk.bbox.join(','))
      const scale = (blk.type === 'image_caption' || blk.type === 'table_caption') ? 0.85 : 1
      return {
        fontSize: `${fs * scale}px`,
        lineHeight: 1.15,
        whiteSpace: 'pre-wrap',
        wordBreak: 'break-word'
      }
    },

    renderBlock (blk) {
      const res = []
      blk.lines.forEach(line => {
        line.spans.forEach(span => {
          if (span.type === 'inline_equation') {
            res.push({
              html: katex.renderToString(span.content, { displayMode: false, throwOnError: false }),
              attrs: { class: 'katex-inline' }
            })
          } else {
            let t = span.content
              .replace(/([\u4e00-\u9fa5])\s+(?=[\u4e00-\u9fa5])/g, '$1')
              .replace(/ /g, '&nbsp;')
              // .replace(/\n/g, '<br/>')
              .replace(/\n/g, '')
            res.push({ html: t, attrs: {} })
          }
        })
        res.push({ html: '<br/>', attrs: {} })
      })
      return res
    },

    calcAllFontSizes () {
      this.layout.pdf_info.forEach(pInfo => {
        this.blocksOf(pInfo, ['text', 'title', 'image_caption', 'table_caption']).forEach(blk => {
          const maxH = (blk.bbox[3] - blk.bbox[1]) * this.pageScale * 0.95
          const maxW = (blk.bbox[2] - blk.bbox[0]) * this.pageScale
          const text = blk.lines.map(l => l.spans.map(s => s.content).join('')).join('')
          let min = this.minFS, max = blk.type === 'title' ? 40 : this.maxFS, best = min
          const cvs = document.createElement('canvas')
          const ctx = cvs.getContext('2d')
          for (let i = 0; i < 6; i++) {
            const mid = (min + max) / 2
            ctx.font = `${mid}px ${getComputedStyle(document.body).fontFamily || 'serif'}`
            let line = '', h = 0, lineH = mid * 1.15
            text.split('').forEach(ch => {
              if (ctx.measureText(line + ch).width > maxW && line) {
                h += lineH; line = ch
              } else { line += ch }
            })
            h += lineH * 1.1
            h <= maxH ? ((best = mid), (min = mid)) : (max = mid)
          }
          this.fontMap.set(blk.bbox.join(','), best)
        })
      })
      this.$forceUpdate()
    }
  },

  directives: {
    scaleKatex: {
      inserted (el, binding) {
        const { content, imagePath } = binding.value || {}
        if (imagePath) {
          const img = document.createElement('img')
          img.src = imagePath
          img.style.maxWidth = '100%'
          img.style.height = 'auto'
          img.onload = () => scaleToBox(el, el.parentElement)
          el.innerHTML = ''
          el.appendChild(img)
        } else {
          el.innerHTML = katex.renderToString(content, { displayMode: true, throwOnError: false })
          scaleToBox(el, el.parentElement)
        }
      },
      update (el, binding) {
        const { content, imagePath } = binding.value || {}
        const old = binding.oldValue || {}
        if (content !== old.content || imagePath !== old.imagePath) {
          if (imagePath) {
            const img = document.createElement('img')
            img.src = imagePath
            img.style.maxWidth = '100%'
            img.style.height = 'auto'
            img.onload = () => scaleToBox(el, el.parentElement)
            el.innerHTML = ''
            el.appendChild(img)
          } else {
            el.innerHTML = katex.renderToString(content, { displayMode: true, throwOnError: false })
            scaleToBox(el, el.parentElement)
          }
        }
      }
    },
    scaleImg: {
      inserted (el) {
        el.onload = () => scaleToBox(el, el.parentElement)
      }
    },
    scaleTable: {
      inserted (el, binding) {
        el.innerHTML = binding.value
        scaleToBox(el, el.parentElement)
      }
    }
  }
}

function scaleToBox (inner, box) {
  const pw = box.clientWidth
  const ph = box.clientHeight
  const iw = inner.offsetWidth || inner.naturalWidth || inner.clientWidth
  const ih = inner.offsetHeight || inner.naturalHeight || inner.clientHeight
  if (!iw || !ih) return
  const scale = Math.min(pw / iw, ph / ih, 1)
  inner.style.transformOrigin = '0 0'
  inner.style.transform = `scale(${scale}) translate(${(pw / scale - iw) / 2}px, ${(ph / scale - ih) / 2}px)`
}
</script>

<style lang="less">
* {
  font-family: "Microsoft YaHei", "微软雅黑", sans-serif;
}

.katex-inline .katex {
  line-height: 0.5 !important;

  .katex-html {
    font-size: clamp(13px, 0.7em, 40px);
  }
}

.katex-display {
  margin: 0;
  margin-right: 40px;

  .tag {
    transform: translateX(100%);
  }
}
</style>

<style scoped lang="less">
.pdf-viewer {
  padding: 20px;
  background: #fafafa;

  /deep/ table {
    border-collapse: collapse;
    border: 1px solid #666;
    width: 100% !important;

    th,
    td {
      border: 1px solid #666;
      padding: 4px 6px;
    }

    th {
      background: #f0f0f0;
      font-weight: bold;
    }
  }
}

.scale-wrapper {
  position: absolute;
  overflow: hidden;
}

.scale-inner {
  display: inline-block;
  transform-origin: 0 0;
}

.text-block {
  // overflow: hidden;
  padding-bottom: 2px;
}

.text-block,
.scale-wrapper {

  .inline-wrapper,
  .scale-inner {
    cursor: pointer;

    &:hover {
      background: #284f6e;
      border-radius: 10px;
      color: #fff;
    }
  }
}
</style>